use std::collections::HashMap;

thread_local! {
    static DISTRICT_INFO: HashMap<u32, &'static str> = parse_district_info();
}

fn parse_district_info() -> HashMap<u32, &'static str> {
    include_str!("data.txt")
        .lines()
        .map(|l| {
            let (code, val) = l.split_once(' ').unwrap();
            (code.parse().unwrap(), val)
        })
        .collect()
}

struct IDInfo {
    district: u32,
    birthday: u32,
    order_code: u16,
}
impl IDInfo {
    // 不实现 FromStr，因为 Option 的糖会更简洁一些
    pub fn from_str(s: &str) -> Option<Self> {
        const PARAMETER: [u8; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
        const CHECK_CODE: &[u8] = b"10X98765432";

        Some(match s.len() {
            15 => {
                // 一代身份证
                let district = s[0..6].parse().ok()?;
                let birthday = 19000000 + s[6..12].parse::<u32>().ok()?;
                let order_code = s[12..].parse().ok()?;
                IDInfo {
                    district,
                    birthday,
                    order_code,
                }
            }
            18 => {
                // 二代身份证
                let district = s[0..6].parse().ok()?;
                let birthday = s[6..14].parse().ok()?;
                let order_code = s[14..17].parse().ok()?;

                let sum = s[..17]
                    .bytes()
                    .zip(PARAMETER.iter())
                    .fold(0, |acc, (c, p)| acc + (c - b'0') as u32 * *p as u32);
                if s.as_bytes()[17] != CHECK_CODE[sum as usize % 11] {
                    return None;
                }
                IDInfo {
                    district,
                    birthday,
                    order_code,
                }
            }
            _ => {
                return None;
            }
        })
    }
}

fn format_birthday(birthday: u32) -> String {
    let year = birthday / 10000;
    let month = birthday / 100 % 100;
    let day = birthday % 100;
    format!("{:04}年{:02}月{:02}日", year, month, day)
}

pub fn check_id_card(id_card_no: &str) -> String {
    let id_info = match IDInfo::from_str(id_card_no) {
        Some(info) => info,
        None => return "身份证号码错误".to_owned(),
    };
    format!(
        "身份证号码正确,{},{},{}",
        if id_info.order_code % 2 == 0 {
            "女"
        } else {
            "男"
        },
        format_birthday(id_info.birthday),
        DISTRICT_INFO.with(|info| { info.get(&id_info.district).copied().unwrap_or("") })
    )
}
